package org.vincen.helloworld.mockito;

import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;

import java.util.LinkedList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import com.google.common.collect.Lists;

public class PractiseMockito {
	
	private List<String> mockedList = null;
	private LinkedList<String> mockedLinkedList = null;
	
	@SuppressWarnings("unchecked")
	@Before
	public void before() {
		// 模拟一个接口
		mockedList = mock(List.class);
		// 模拟一个类
		mockedLinkedList = mock(LinkedList.class);
	}
	
	/**
	 * 测试行为
	 */
	@Test
	public void testBehavior(){
		// 调用add一次
		mockedList.add("once");
		// 下面两个写法验证效果一样，均验证add方法是否被调用了一次
		//verify的用法：verify(mockedList).add("twice");即会报错，因为，从来没有向list加入过twice
		//Argument(s) are different! Wanted: linkedList.add("twice");
//		verify(mockedList).add("twice");
		//following two verifications work exactly the same - times(1) is used by default
		verify(mockedList).add("once");
		verify(mockedList, times(1)).add("once");
		//测试方法调用次数
		mockedList.add("twice");
		mockedList.add("twice");

		mockedList.add("three times");
		mockedList.add("three times");
		mockedList.add("three times");

		//exact number of invocations verification
		verify(mockedList, times(2)).add("twice");
		verify(mockedList, times(3)).add("three times");

		//verification using never(). never() is an alias to times(0)
		verify(mockedList, never()).add("never happened");
		verify(mockedList, times(0)).add("neveer happend");

		//verification using atLeast()/atMost()
		verify(mockedList, atLeastOnce()).add("three times");
		//测试最少向mockedList中加入2次 five times，此处报错，因为，没有放入five times
//		verify(mockedList, atLeast(2)).add("five times");
		//测试最多向mockedList中加入5次 three times
		verify(mockedList, atMost(5)).add("three times");
		//测试无返回值的方法调用
		doNothing().when(mockedList).clear();
	}

	/**
	 * 测试桩
	 */
	@Test(expected=RuntimeException.class)
	public void testStubbing(){
		// 此时调用get方法，会返回null，因为还没有对方法调用的返回值做模拟
		assertNull(mockedLinkedList.get(999));
		// 模拟获取第一个元素时，返回字符串first。给特定的方法调用返回固定值在官方说法中称为stub。
		when(mockedLinkedList.get(0)).thenReturn("first");
		// 此时打印输出first
		assertEquals("first", mockedLinkedList.get(0));
//		// 模拟获取第二个元素时，抛出RuntimeException  
		when(mockedLinkedList.get(1)).thenThrow(new RuntimeException());   
//		// 此时将会抛出RuntimeException
//		mockedLinkedList.get(1);
		// 没有返回值类型的方法也可以模拟异常抛出：
		doThrow(new RuntimeException("Mock测试，模拟抛出的异常")).when(mockedLinkedList).clear();
		//运行此代码，会抛出异常，在@Test注解中捕获了。
		mockedLinkedList.clear();
		
	}

	/**
	 * 测试参数匹配
	 */
	@Test
	public void testMatchers(){
		// anyInt()匹配任何int参数，这意味着参数为任意值，其返回值均是element
		when(mockedList.get(anyInt())).thenReturn("element");
		// 此时打印是element
		assertEquals("element", mockedList.get(752));
		//you can also verify using an argument matcher
		verify(mockedList).get(anyInt());
		//此处需要自己实现Matcher接口
		//在ArgumentMatcher类的api中也有相关教程
		when(mockedList.contains(argThat(new ArgumentMatcher<Context>() {
			@Override
			public boolean matches(Object argument) {
				if(argument instanceof Context){
					return true;
				}
				return false;
			}
		}))).thenReturn(true);
		//由于此处没有传给contains方法Context类型的参数，所以matches方法会返回false
		//这是上边的自定义匹配器的另一种写法。
		assertEquals(false, mockedList.contains("1"));
		MyClass mockedMyClass = mock(MyClass.class);
		doReturn("abc").when(mockedMyClass).doViews(argThat(new ArgumentMatcher<Context>() {
			@Override
			public boolean matches(Object argument) {
				if(argument instanceof Context){
					System.out.println(argument.toString());
					return true;
				}
				return false;
			}
		}));
		assertEquals("abc", mockedMyClass.doViews(new Context("vin", "cen")));
		//较复杂的参数匹配将会降低测试代码的可读性。有时实现参数对象的equals()方法是
		//个不错的选择（Mockito默认使用equals()方法进行参数匹配），它可以使测试代码
		//更为整洁。另外，有些场景使用参数捕获器（ArgumentCaptor）要比自定义参数匹配
		//器更加合适。
	}

	/**
	 * 测试方法调用顺序
	 */
	@SuppressWarnings("unchecked")
	@Test
	public void testOrder(){
		// A. Single mock whose methods must be invoked in a particular order
		List<String> singleMock = mock(List.class);
		//using a single mock
		singleMock.add("was added first");
		singleMock.add("was added second");
		//create an inOrder verifier for a single mock
		InOrder inOrder = inOrder(singleMock);
		//following will make sure that add is first called with "was added first, then with "was added second"
		inOrder.verify(singleMock).add("was added first");
		inOrder.verify(singleMock).add("was added second");

		// B. Multiple mocks that must be used in a particular order
		List<String> firstMock = mock(List.class);
		List<String> secondMock = mock(List.class);
		//using mocks
		firstMock.add("was called first");
		secondMock.add("was called second");
		//create inOrder object passing any mocks that need to be verified in order
		InOrder inOrder2 = inOrder(firstMock, secondMock);
		//following will make sure that firstMock was called before secondMock
		//颠倒下面两条语句的执行顺序，会报：org.mockito.exceptions.verification.VerificationInOrderFailure异常
		inOrder2.verify(firstMock).add("was called first");
		inOrder2.verify(secondMock).add("was called second");
		// Oh, and A + B can be mixed together at will
	}

	/**
	 * 测试自定义返回值
	 */
	@Test
	public void testAnswer(){
		when(mockedLinkedList.get(4)).thenAnswer(new Answer<String>() {
			public String answer(InvocationOnMock invocation) throws Throwable {
				Object[] objs = invocation.getArguments();
				Integer num = (Integer)objs[0];
				if (num > 3){
					return "yes";
				} else {
					throw new RuntimeException();
				}
			}
		});
	}

	/**
	 * 测试参数捕获器
	 */
	@Test
	public void testArgumentCaptor(){
		mockedLinkedList.add("john");
		mockedLinkedList.add("james");
		ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
		verify(mockedLinkedList, times(2)).add(captor.capture());
		//方法调用有多个参数时，只会返回最后一个
		assertEquals("james", captor.getValue());
		//捕获所有的参数
		assertEquals(Lists.newArrayList("john", "james"), captor.getAllValues());
	}

	/**
	 * 测试监视对象
	 */
	@Test
	public void testSpy() {
		List<String> list = new LinkedList<String>();
		List<String> spy = spy(list);
		when(spy.size()).thenReturn(100);
		spy.add("one");
		spy.add("two");
		assertEquals("one", spy.get(0));
		assertEquals(100, spy.size());
		verify(spy).add("one");
		verify(spy).add("two");
	}
}